// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package strconv

import (
	"strconv"
	"unicode/utf8"
)

// NB: predefined strconv constants
const (
	tx = 0x80 // 1000 0000
	t2 = 0xC0 // 1100 0000
	t3 = 0xE0 // 1110 0000
	t4 = 0xF0 // 1111 0000

	maskx = 0x3F // 0011 1111

	rune1Max = 1<<7 - 1
	rune2Max = 1<<11 - 1
	rune3Max = 1<<16 - 1

	runeError = '\uFFFD'     // the "error" Rune or "Unicode replacement character"
	maxRune   = '\U0010FFFF' // Maximum valid Unicode code point.

	// NB: Code points in the surrogate range are not valid for UTF-8.
	surrogateMin = 0xD800
	surrogateMax = 0xDFFF

	lowerhex = "0123456789abcdef"

	quote = byte('"')
)

// EncodeRune writes into src (which must be large enough) the UTF-8 encoding
// of the rune at the given index. It returns the number of bytes written.
//
// NB: based on utf8.encodeRune method, but instead uses indexed insertion
// into a predefined buffer.
func encodeRune(dst []byte, r rune, idx int) int {
	// Negative values are erroneous. Making it unsigned addresses the problem.
	switch i := uint32(r); {
	case i <= rune1Max:
		dst[idx] = byte(r)
		return idx + 1
	case i <= rune2Max:
		dst[idx] = t2 | byte(r>>6)
		dst[idx+1] = tx | byte(r)&maskx
		return idx + 2
	case i > maxRune, surrogateMin <= i && i <= surrogateMax:
		r = runeError
		fallthrough
	case i <= rune3Max:
		dst[idx] = t3 | byte(r>>12)
		dst[idx+2] = tx | byte(r)&maskx
		dst[idx+1] = tx | byte(r>>6)&maskx
		return idx + 3
	default:
		dst[idx] = t4 | byte(r>>18)
		dst[idx+1] = tx | byte(r>>12)&maskx
		dst[idx+2] = tx | byte(r>>6)&maskx
		dst[idx+3] = tx | byte(r)&maskx
		return idx + 4
	}
}

// It returns the number of bytes written.
func insertEscapedRune(dst []byte, r rune, idx int) int {
	if r == rune(quote) || r == '\\' { // always backslashed
		dst[idx] = '\\'
		dst[idx+1] = byte(r)
		return idx + 2
	}

	if strconv.IsPrint(r) {
		return encodeRune(dst, r, idx)
	}

	switch r {
	case '\a':
		dst[idx] = '\\'
		dst[idx+1] = 'a'
		return idx + 2
	case '\b':
		dst[idx] = '\\'
		dst[idx+1] = 'b'
		return idx + 2
	case '\f':
		dst[idx] = '\\'
		dst[idx+1] = 'f'
		return idx + 2
	case '\n':
		dst[idx] = '\\'
		dst[idx+1] = 'n'
		return idx + 2
	case '\r':
		dst[idx] = '\\'
		dst[idx+1] = 'r'
		return idx + 2
	case '\t':
		dst[idx] = '\\'
		dst[idx+1] = 't'
		return idx + 2
	case '\v':
		dst[idx] = '\\'
		dst[idx+1] = 'v'
		return idx + 2
	default:
		switch {
		case r < ' ':
			dst[idx] = '\\'
			dst[idx+1] = 'x'
			dst[idx+2] = lowerhex[byte(r)>>4]
			dst[idx+3] = lowerhex[byte(r)&0xF]
			return idx + 4
		case r > utf8.MaxRune:
			r = 0xFFFD
			fallthrough
		case r < 0x10000:
			dst[idx] = '\\'
			dst[idx+1] = 'u'
			dst[idx+2] = lowerhex[r>>uint(12)&0xF]
			dst[idx+3] = lowerhex[r>>uint(8)&0xF]
			dst[idx+4] = lowerhex[r>>uint(4)&0xF]
			dst[idx+5] = lowerhex[r>>uint(0)&0xF]
			return idx + 6
		default:
			dst[idx] = '\\'
			dst[idx+1] = 'U'
			dst[idx+2] = lowerhex[r>>uint(28)&0xF]
			dst[idx+3] = lowerhex[r>>uint(24)&0xF]
			dst[idx+4] = lowerhex[r>>uint(20)&0xF]
			dst[idx+5] = lowerhex[r>>uint(16)&0xF]
			dst[idx+6] = lowerhex[r>>uint(12)&0xF]
			dst[idx+7] = lowerhex[r>>uint(8)&0xF]
			dst[idx+8] = lowerhex[r>>uint(4)&0xF]
			dst[idx+9] = lowerhex[r>>uint(0)&0xF]
			return idx + 10
		}
	}
}

// Escape copies byte slice src to dst at a given index, adding escaping any
// quote or control characters. It returns the index at which the copy finished.
//
// NB: ensure that dst is large enough to store src, additional
// quotation runes, and any additional escape characters.
// as generated by Quote, to dst and returns the extended buffer.
func Escape(dst, src []byte, idx int) int {
	// nolint
	for width := 0; len(src) > 0; src = src[width:] {
		r := rune(src[0])
		width = 1
		if r >= utf8.RuneSelf {
			r, width = utf8.DecodeRune(src)
		}

		if width == 1 && r == utf8.RuneError {
			dst[idx] = '\\'
			dst[idx+1] = 'x'
			dst[idx+2] = lowerhex[src[0]>>4]
			dst[idx+3] = lowerhex[src[0]&0xF]
			idx += 4
			continue
		}

		idx = insertEscapedRune(dst, r, idx)
	}

	return idx
}

// Quote copies byte slice src to dst at a given index, adding
// quotation runes around the src slice and escaping any quote or control
// characters. It returns the index at which the copy finished.
//
// NB: ensure that dst is large enough to store src, additional
// quotation runes, and any additional escape characters.
// as generated by Quote, to dst and returns the extended buffer.
//
// NB: based on stconv.Quote method, but instead uses indexed insertion
// into a predefined buffer.
func Quote(dst, src []byte, idx int) int {
	dst[idx] = quote
	idx++
	idx = Escape(dst, src, idx)
	dst[idx] = quote
	return idx + 1
}

// QuoteSimple copies byte slice src to dst at a given index, adding
// quotation runes around the src slice, but does not escape any
// characters. It returns the index at which the copy finished.
//
// NB: ensure that dst is large enough to store src and two other characters.
func QuoteSimple(dst, src []byte, idx int) int {
	dst[idx] = quote
	idx++
	idx += copy(dst[idx:], src)
	dst[idx] = quote
	return idx + 1
}

// EscapedLength computes the length required for a byte slice to hold
// a quoted byte slice.
//
// NB: essentially a dry-run of `Escape` that does not write characters, but
// instead counts total character counts for the destination byte slice.
func EscapedLength(src []byte) int {
	length := 0
	// nolint
	for width := 0; len(src) > 0; src = src[width:] {
		r := rune(src[0])
		width = 1
		if r >= utf8.RuneSelf {
			r, width = utf8.DecodeRune(src)
		}

		if width == 1 && r == utf8.RuneError {
			length += 4
			continue
		}

		length += escapedRuneLength(r)
	}

	return length
}

// QuotedLength computes the length required for a byte slice to hold
// a quoted byte slice.
//
// NB: essentially a dry-run of `Quote` that does not write characters, but
// instead counts total character counts for the destination byte slice.
func QuotedLength(src []byte) int {
	return 2 + EscapedLength(src) // account for opening and closing quotes
}

func escapedRuneLength(r rune) int {
	if r == rune(quote) || r == '\\' { // always backslashed
		return 2
	}

	if strconv.IsPrint(r) {
		switch i := uint32(r); {
		case i <= rune1Max:
			return 1
		case i <= rune2Max:
			return 2
		case i > maxRune, surrogateMin <= i && i <= surrogateMax:
			fallthrough
		case i <= rune3Max:
			return 3
		default:
			return 4
		}
	}

	switch r {
	case '\a':
		return 2
	case '\b':
		return 2
	case '\f':
		return 2
	case '\n':
		return 2
	case '\r':
		return 2
	case '\t':
		return 2
	case '\v':
		return 2
	default:
		switch {
		case r < ' ':
			return 4
		case r > utf8.MaxRune:
			fallthrough
		case r < 0x10000:
			return 6
		default:
			return 10
		}
	}
}
